package org.hackystat.iw.projectoverviewer.page.treemap;

/*
 * This code is highly inspired by Object Lab.
 * 
 * www.ObjectLab.co.uk
 */

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.StringReader;
import java.util.LinkedHashMap;
import javax.swing.BorderFactory;
import javax.swing.JComboBox;
import javax.swing.JApplet;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTree;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.EtchedBorder;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultTreeModel;
import net.sf.jtreemap.swing.ColorProvider;
import net.sf.jtreemap.swing.IToolTipBuilder;
import net.sf.jtreemap.swing.JTreeMap;
import net.sf.jtreemap.swing.SplitByNumber;
import net.sf.jtreemap.swing.SplitBySlice;
import net.sf.jtreemap.swing.SplitBySortedWeight;
import net.sf.jtreemap.swing.SplitSquarified;
import net.sf.jtreemap.swing.SplitStrategy;
import net.sf.jtreemap.swing.TreeMapNode;
import net.sf.jtreemap.swing.provider.RandomColorProvider;
import net.sf.jtreemap.swing.provider.ZoomPopupMenu;

/**
 * Test of JTreeMap
 * 
 * @author Laurent Dutheil
 * @author Jianfei Liao
 * @author Nathan Britton 
 */
public class JTreeMapApp extends JApplet implements ActionListener {

  private static final int SCROLLPANE_WIDTH = 140;

  private static final int APPLICATION_HEIGHT = 400;

  private static final int APPLICATION_WIDTH = 600;

  private static final int DEFAULT_FONT_SIZE = 16;

  private static final long serialVersionUID = 1L;

  private static final String CREATE_NEW_PROJECT = "Create A New Project";

  private JTreeMap jTreeMap;

  private JTree treeView = new JTree();

  private BuilderTM3 builderTM3;

  private CardLayout cardLayout;

  private JComboBox cmbColorProvider;

  private JComboBox cmbStrategy;

  private JComboBox cmbValue;

  private JComboBox cmbWeight;

  private final LinkedHashMap<String, ColorProvider> colorProviders = 
      new LinkedHashMap<String, ColorProvider>();

  private JPanel panelLegend;

  private JPanel panelTM3;

  private TreeMapNode root;

  private final LinkedHashMap<String, SplitStrategy> strategies = 
      new LinkedHashMap<String, SplitStrategy>();

  private DefaultTreeModel treeModel;

  /**
   * Initialization. Required for the applet, if uses a constructor would result
   * null exception for getParameter(..) method and such.
   */
  public void init() {
    System.out.println("Enter init()");
    // Set system look and feel
    try {
      UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
    }
    catch (ClassNotFoundException e) {
      e.printStackTrace();
    }
    catch (InstantiationException e) {
      e.printStackTrace();
    }
    catch (IllegalAccessException e) {
      e.printStackTrace();
    }
    catch (UnsupportedLookAndFeelException e) {
      e.printStackTrace();
    }
    
    /* uncomment if you want to keep proportions on zooming */
    // jTreeMap.setZoomKeepProportion(true);
   

    // init GUI and try to redirect the treemap to the correct source file
    String fileContent = null;
    try {
      fileContent = getParameter("FileContent");
    }
    catch (Exception e) {
      // for testing purpose
      fileContent = "TestMetric\r\nINTEGER\r\n1\t\tTestProject\tTestFile.txt\r\n";
    }
    
    try {
      fileContent = fileContent.replaceAll("@TAB@", "\t");
      fileContent = fileContent.replaceAll("@NEWLINE@", "\r\n");
      
      builderTM3 = new BuilderTM3(new BufferedReader(new StringReader(fileContent)));
      root = builderTM3.getRoot();

      if (root == null) {
        System.out.println("root is null");
      }
      else {
        jTreeMap = new JTreeMap(this.root, treeView);
        IToolTipBuilder myToolTip = new IWToolTipBuilder(jTreeMap, "", "", true);
        
        jTreeMap.setToolTipBuilder(myToolTip);
        jTreeMap.setFont(new Font(null, Font.BOLD, DEFAULT_FONT_SIZE));
        jTreeMap.setPreferredSize(new Dimension(APPLICATION_WIDTH, APPLICATION_HEIGHT));
        jTreeMap.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.LOWERED));
        jTreeMap.setRoot(this.root);
        
        treeModel = new DefaultTreeModel(this.root);
        treeModel.setRoot(this.root); 
        
        //change the max border between two nodes of the same level
        TreeMapNode.setBorder(11);
        // add a popup menu to zoom the JTreeMap
        new ZoomPopupMenu(this.jTreeMap);

        initGUI();
        
        setTM3Fields();
        panelTM3.setVisible(true);
        
        // set the default metrics 
        int strategyIndex = Integer.parseInt(getParameter("Strategy"));
        this.cmbStrategy.setSelectedIndex(strategyIndex);
        
        int colorProviderIndex = Integer.parseInt(getParameter("ColorProvider"));
        this.cmbColorProvider.setSelectedIndex(colorProviderIndex);
        
        int sizeIndex = Integer.parseInt(getParameter("Size"));
        this.cmbWeight.setSelectedIndex(sizeIndex);
        
        int colorIndex = Integer.parseInt(getParameter("Color"));
        this.cmbValue.setSelectedIndex(colorIndex);
      }
    }
    catch (Exception e) {
      e.printStackTrace();
    }
    System.out.println("Exit init()");
  }

  /**
   * Constructor for running as application.
   */
  public JTreeMapApp() {
    // does nothing
  }
  
/*  *//**
   * main method for running as application.
   * 
   * @param args command line
   *//*
  public static void main(final String[] args) {
    Component app = new JTreeMapApp();
    JFrame frame = new JFrame("JTreeMap Demo");
    frame.getContentPane().add(app);

    frame.setVisible(true);
    frame.pack();
  }*/

  /**
   * Invokes when the user performs action from the menu.
   * 
   * @param e tag for the user input command
   * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
   */
  public void actionPerformed(final ActionEvent e) {
    // Action performed for the File Menu @see addMenu()

    final String command = e.getActionCommand();

    if (CREATE_NEW_PROJECT.equals(command)) {
      String fileContent = getParameter("FileContent");
      
      try {
        fileContent = fileContent.replaceAll("@TAB@", "\t");
        fileContent = fileContent.replaceAll("@NEWLINE@", "\r\n");
        System.out.println("4: " + fileContent);
        
        StringReader sr = null;
        BufferedReader br = null;
        sr = new StringReader(fileContent);
        br = new BufferedReader(sr);
        
        builderTM3 = new BuilderTM3(br);
        root = builderTM3.getRoot();
      
        if (root == null) {
          System.out.println("root is null");
        }
        else {
          jTreeMap.setRoot(this.root);
          treeModel.setRoot(this.root);
  
          initGUI();
          
          setTM3Fields();
          panelTM3.setVisible(true);
        }
      }
      catch (Exception ex) {
        ex.printStackTrace();
      }
    }
  }

  /**
   * Add a splitPane with a treeview on the left and the JTreeMap on the right
   * 
   * @param parent the container to add the panel to
   */
  private void addTreePanel(final Container parent) {
    final JSplitPane splitPaneCenter = new JSplitPane();
    splitPaneCenter.setBorder(BorderFactory.createEmptyBorder());
    parent.add(splitPaneCenter, BorderLayout.CENTER);

    final JScrollPane jScrollPane1 = new JScrollPane();
    splitPaneCenter.setTopComponent(jScrollPane1);
    splitPaneCenter.setBottomComponent(this.jTreeMap);

    treeModel = new DefaultTreeModel(this.root);
    treeView = new JTree(this.treeModel);
    jTreeMap.setTreeView(treeView);
    jScrollPane1.getViewport().add(this.treeView);
    jScrollPane1.setPreferredSize(new Dimension(SCROLLPANE_WIDTH, jTreeMap.getRoot().getHeight()));
    treeView.addTreeSelectionListener(new TreeSelectionListener() {
      public void valueChanged(final TreeSelectionEvent e) {
        // for each selected elements ont the treeView, we zoom the JTreeMap
        TreeMapNode dest = (TreeMapNode) JTreeMapApp.this.treeView.getLastSelectedPathComponent();

        // if the element is a leaf, we select the parent
        if (dest != null && dest.isLeaf()) {
          dest = (TreeMapNode) dest.getParent();
        }
        if (dest == null) {
          return;
        }

        JTreeMapApp.this.jTreeMap.zoom(dest);
        JTreeMapApp.this.jTreeMap.repaint();
      }
    });
  }

  /**
   * Add a pane to choose the weight and the value for TM3 file
   * 
   * @param parent the container to add the panel to
   */
  private void addTM3Panel(final Container parent) {
    panelTM3 = new JPanel();
    panelTM3.setBorder(BorderFactory.createEmptyBorder());
    final JLabel lblWeight = new JLabel("Size:");
    parent.add(this.panelTM3, FlowLayout.RIGHT);
    panelTM3.add(lblWeight);
    
    if (cmbWeight == null) {
      cmbWeight = new JComboBox();
    }
    panelTM3.add(this.cmbWeight);
    cmbWeight.addActionListener(new ActionListener() {
      public void actionPerformed(final ActionEvent e) {
        final JComboBox cmb = (JComboBox) e.getSource();
        final String field = (String) cmb.getSelectedItem();
        JTreeMapApp.this.builderTM3.setWeights(field);
        JTreeMapApp.this.repaint();
      }
    });
    
    final JLabel lblValue = new JLabel("Color:");
    panelTM3.add(lblValue);

    if (cmbValue == null) {
      cmbValue = new JComboBox();
    }
    panelTM3.add(this.cmbValue);
    cmbValue.addActionListener(new ActionListener() {
      public void actionPerformed(final ActionEvent e) {
        final JComboBox cmb = (JComboBox) e.getSource();
        final String field = (String) cmb.getSelectedItem();
        JTreeMapApp.this.builderTM3.setValues(field);
        createColorProviders();
        updateLegendPanel();
        JTreeMapApp.this.repaint();
      }
    });
  }

  /**
   * add a combobox with all strategies
   * 
   * @param parent the container to add the combo box to
   */
  private void addStragtegyPanel(final Container parent) {
    final JPanel strategyPanel = new JPanel();
    strategyPanel.setBorder(BorderFactory.createEmptyBorder());
    final JLabel lblStrategy = new JLabel("Layout Strategy:");
    parent.add(strategyPanel, FlowLayout.LEFT);
    strategyPanel.add(lblStrategy);
    cmbStrategy = new JComboBox();
    strategyPanel.add(this.cmbStrategy);

    createStrategies();

    cmbStrategy.addActionListener(new ActionListener() {
      public void actionPerformed(final ActionEvent e) {
        updateStrategy();
      }
    });
  }

  /**
   * add a combobox with all color providers and the legend panel
   * 
   * @param parent the container to add the panel to
   */
  private void addColorProviderPanel(final Container parent) {
    final JPanel colorProviderPanel = new JPanel();
    colorProviderPanel.setLayout(new BorderLayout());
    final JPanel jPanelLegendNorth = new JPanel();
    final JLabel lblColorProvider = new JLabel("Color Provider:");
    jPanelLegendNorth.add(lblColorProvider);
    cmbColorProvider = new JComboBox();
    jPanelLegendNorth.add(this.cmbColorProvider);
    parent.add(jPanelLegendNorth, FlowLayout.CENTER);
    panelLegend = new JPanel();
    this.getContentPane().add(this.panelLegend, BorderLayout.SOUTH);
    parent.add(colorProviderPanel, BorderLayout.SOUTH);
    cardLayout = new CardLayout();
    panelLegend.setLayout(this.cardLayout);

    createColorProviders();

    for (final String key : colorProviders.keySet()) {
      cmbColorProvider.addItem(key);
    }

    cmbColorProvider.addActionListener(new ActionListener() {
      public void actionPerformed(final ActionEvent e) {
        if (JTreeMapApp.this.cmbColorProvider.getSelectedIndex() > -1) {
          updateLegendPanel();
        }
      }
    });
  }

  /**
   * Create the colors to be used for the "value" aspect of the map
   */
  private void createColorProviders() {
    colorProviders.put("RedGreen Linear", new RedYellowGreenColorProvider(jTreeMap, 
        RedYellowGreenColorProvider.ColorDistributionTypes.Linear));
    colorProviders.put("RedGreen Logarithmic", new RedYellowGreenColorProvider(jTreeMap, 
        RedYellowGreenColorProvider.ColorDistributionTypes.Log));
    colorProviders.put("RedGreen SquareRoot", new RedYellowGreenColorProvider(jTreeMap, 
        RedYellowGreenColorProvider.ColorDistributionTypes.SquareRoot));
    colorProviders.put("RedGreen CubeRoot", new RedYellowGreenColorProvider(jTreeMap, 
        RedYellowGreenColorProvider.ColorDistributionTypes.CubicRoot));
    colorProviders.put("Random", new RandomColorProvider(jTreeMap));
    for (final String key : colorProviders.keySet()) {
      final ColorProvider cp = colorProviders.get(key);
      panelLegend.add(cp.getLegendPanel(), key);
    }
  }

  /**
   * Create different "strategies" for laying out the boxes in the map
   */
  private void createStrategies() {
    strategies.put("Squarified", new SplitSquarified());
    strategies.put("Sorted Weight", new SplitBySortedWeight());
//    strategies.put("Weight", new SplitByWeight());
    strategies.put("Slice", new SplitBySlice());
    strategies.put("Equal Weight", new SplitByNumber());
    cmbStrategy.removeAllItems();
    for (final String key : strategies.keySet()) {
      cmbStrategy.addItem(key);
    }
  }

  /**
   * init the window
   * 
   * @throws Exception for any unforseen OS errors
   */
  private void initGUI() throws Exception {
    JPanel topPanel = new JPanel();
    add(topPanel, BorderLayout.NORTH);
    
    // panel to choose the strategy
    addStragtegyPanel(topPanel);
    // splitPane with treeView on the left and JTreeMap on the right
    addTreePanel(this.getContentPane());
    // panel to choose the color provider
    addColorProviderPanel(topPanel);
    // panel to choose the fields for a TM3 file
    addTM3Panel(topPanel);
    
    // update the chosen strategy
    updateStrategy();
    // update the chosen color provider
    updateLegendPanel();
  }

  /**
   * Uses the tm3 file to establish the number of files that require a field in the map
   */
  private void setTM3Fields() {
    final String[] numberFields = builderTM3.getNumberFields();
    final String[] cmbValues = new String[numberFields.length + 1];
    cmbValues[0] = "";
    for (int i = 1; i < cmbValues.length; i++) {
      cmbValues[i] = numberFields[i - 1];
    }
    if (cmbWeight == null) {
      cmbWeight = new JComboBox();
    }
    if (cmbValue == null) {
      cmbValue = new JComboBox();
    }
    cmbValue.removeAllItems();
    cmbWeight.removeAllItems();
    for (final String item : cmbValues) {
      cmbWeight.addItem(item);
      cmbValue.addItem(item);
    }
  }

  /**
   * Refresh the legend panel with the information for the selected strategy
   */
  private void updateLegendPanel() {
    final String key = (String) cmbColorProvider.getSelectedItem();
    final ColorProvider cp = colorProviders.get(key);
    if (cp != null) {
      jTreeMap.setColorProvider(cp);
      cardLayout.show(this.panelLegend, key);
    }
    JTreeMapApp.this.repaint();
  }

  /**
   * Reorganize the map based on the selected strategy
   */
  private void updateStrategy() {
    final String key = (String) cmbStrategy.getSelectedItem();
    final SplitStrategy strat = strategies.get(key);
    jTreeMap.setStrategy(strat);
    jTreeMap.repaint();
  }
}
